#!/usr/bin/env python
"""
slide.py - "Sorta like Python" IDE
(I know it's not really an IDE, but the Slip and Slide thing was to cool to resist)

Last Updated: 
	
	$Date: 2002/04/19 20:54:17 $

License: 

	MIT License

	Copyright (c) 2002 Scott Sweeney
	
	Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
	documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
	the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
	and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
	
	The above copyright notice and this permission notice shall be included in all copies 
	or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
	BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
	DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	

Description:
					 
	This script is a terminal interface for working on XML files via SLiP ("Sorta Like Python") 
	shorthand syntax.  

	The program opens the a XML file, and renders it as temporary SLIP file for easy 
	and quick editing in your editor of choice.  You can choose the editor by creating a slide.conf file, via the interface, 
	or by customizing editorcmd config default.  Same goes for the preview viewer variable.  
	
	When the content is saved, it save the content as XML.

Usage:

	With optional file path:
		python slide /path/to/file.xml
	Or by self..
		python slide.py

"""
__version__ = '0.2'


import sliplib, tempfile, sys, os, string, time, ConfigParser, codecs, re

booVerbose = 1

pathCFG = "slide.cfg"
pathLastFileDir = os.getcwd()

strSlipExtension = ".slp"
strXMLExtension = ".xml"

strHelperAppsConfigLabel = "helperapps"
strEditorConfigLabel = "editorcmd"
strViewerConfigLabel = "viewercmd"
strDefaultEditorCmd = "notepad"
strDefaultViewerCmd = "explorer"

#get configs for editing and viewing apps
config = ConfigParser.ConfigParser()
config.add_section(strHelperAppsConfigLabel )

d = config.defaults()
d[strEditorConfigLabel ] = strDefaultEditorCmd
d[strViewerConfigLabel] = strDefaultViewerCmd


try:
	config.read(pathCFG)
except:
	if booVerbose:
		print "No config file, loading defaults...\n\n\n"


strEditorCmd = config.get(strHelperAppsConfigLabel , strEditorConfigLabel)
strViewerCmd = config.get(strHelperAppsConfigLabel , strViewerConfigLabel)



objCurrDocInstance = None


def swapLineBreakers(strText, strOldLineSep, strNewLineSep):
	strText = string.replace(strText, strOldLineSep, strNewLineSep)
	return strText


class clsDocInstance:
	def __init__(self):
		self.XML = u""
		self.SLIP = u""
		self.path = ""
		self.XMLencoding = "" 
		self.SLIPencoding = "" 
		self.XMLlinesep = u""
		#self.SLIPlinesep = "" #not used... yet
		
class EncodingError(Exception):
	pass
		

openDialog = None
saveAsDialog = None


def tkOpenFile():
	import tkFileDialog, Tkinter
	global pathLastFileDir
	this = Tkinter.Tk()
	this.deiconify()
	strPath = tkFileDialog.askopenfilename(initialdir=pathLastFileDir)
	if strPath != "":
		pathLastFileDir = os.path.split(strPath)[0]
	this.destroy()
	return strPath
	
def tkSaveAsFile():
	import tkFileDialog, Tkinter
	global pathLastFileDir
	this = Tkinter.Tk()
	this.deiconify()
	strPath = tkFileDialog.asksaveasfilename(initialdir=pathLastFileDir)
	if strPath != "":
		pathLastFileDir = os.path.split(strPath)[0]
	this.destroy()
	return strPath


def main(pathGiven=""):
	global objCurrDocInstance, strEditorCmd, strViewerCmd

	if pathGiven != "":
		openFile(pathGiven)
		
	
		
	while 1:
		if objCurrDocInstance != None:
			if objCurrDocInstance.path == "":
				strDisplayPath = "[temp location, not saved]"
			else:
				strDisplayPath = objCurrDocInstance.path
				
			strMenu = """
			
			




-----------------------------------------
 SLIDE (a really crude SLIP editor) 
-----------------------------------------

*** Options Menu ***

Current working file: %s

Options:
	N	New File for Editing
	O	Open Another File
	C	Close current file
	E	Edit current file with "%s"
	P 	Preview current file with "%s"
	S	Save current file
	M	Save current file as..
	CE	Change Editor
	CV	Change Viewer
	Q	Quit Slide	
	
Your Choice >> """ % (strDisplayPath, strEditorCmd, strViewerCmd)

		else:
			strMenu = """
			
			
			
			
			
			
			
			
			
-----------------------------------------
 SLIDE (a really crude SLIP editor) 
-----------------------------------------

*** Options Menu ***
		
Current working file: None	
		
	Options:
	N	New File for Editing
	O	Open Existing File for editing
	Q	Quit Slide

Your Choice >> """

		strInput = raw_input(strMenu)


		if string.upper(strInput) == "N":
			if objCurrDocInstance != None:
				strContinue = raw_input("Will close currently open file.  Are you sure you want to? (Y or N) >> ")
				if string.upper(strContinue) == "Y":
					closeFile()
					newFile()
			else:
				newFile()
			
		elif string.upper(strInput) == "O":
			if objCurrDocInstance != None:
				strContinue = raw_input("Will close currently open file.  Are you sure you want to? (Y or N) >> ")
				if string.upper(strContinue) == "Y":
					closeFile()
					#strPath = raw_input("Path to file: ")
					strPath = tkOpenFile()
					intSuccess = openFile(strPath)

			else:
				print "Path to file: "
				#strPath = raw_input("Path to file: ")
				strPath = tkOpenFile()
				
				openFile(strPath)
			
		elif string.upper(strInput) == "C":
			if objCurrDocInstance == None:
				print "No file to Close"
			else:
				closeFile()
				
		elif string.upper(strInput) == "E":
			if objCurrDocInstance == None:
				print "No file to Edit"
			else:
				editFile()
				
		elif string.upper(strInput) == "P":
			if objCurrDocInstance == None:
				print "No file to Preview"
			else:
				previewFile()
		
		elif string.upper(strInput) == "S":
			if objCurrDocInstance == None:
				print "No file to Save"
			elif objCurrDocInstance.path == "":
				#strPath = raw_input("Path to save file as: ")
				strPath = tkSaveAsFile()
				objCurrDocInstance.path = strPath
				saveAsFile(strPath)
			else:
				saveFile()
		
		elif string.upper(strInput) == "M":
			if objCurrDocInstance == None:
				print "No file to Save"
			else:
				#strPath = raw_input("Path to save file as: ")
				strPath = tkSaveAsFile()
				objCurrDocInstance.path = strPath
				saveAsFile(strPath)
				
		elif string.upper(strInput) == "CE":
			print "Enter the command for the editor as if you were to open it from the command line"
			print "ex. \"c:\\\\Program Files\\notepad.exe\" "
			strCommand = raw_input("New command: ")
			changeEditor(strCommand)
			
		elif string.upper(strInput) == "CV":
			print "Enter the command for the viewer as if you were to open it from the command line"
			print "ex. \"c:\\\\Program Files\\xmlViewer.exe\" "
			strCommand = raw_input("New command: ")
			changeViewer(strCommand)
			
		
		elif string.upper(strInput) == "Q":
			if objCurrDocInstance != None:
				strContinue = raw_input("Will close currently open file.  Are you sure you want to? (Y or N) >> ")
				if string.upper(strContinue) == "Y":
					closeFile()
					break
			else:
				break
		
		
		else:
			print "Unrecognized input.  Please try again."




def changeEditor(strCommand):
	global strEditorCmd
	config.set(strHelperAppsConfigLabel, strEditorConfigLabel, strCommand)
	fs = open(pathCFG, "w")
	config.write(fs)
	fs.close()
	strEditorCmd = strCommand


def changeViewer(strCommand):
	global strViewerCmd
	config.set(strHelperAppsConfigLabel, strViewerConfigLabel, strCommand)
	fs = open(pathCFG, "w")
	config.write(fs)
	fs.close()
	strViewerCmd = strCommand


def newFile():
	global objCurrDocInstance
	objCurrDocInstance = clsDocInstance()

	objCurrDocInstance.XML = u""
	objCurrDocInstance.SLIP = u""
	objCurrDocInstance.XMLencoding = sys.getdefaultencoding()
	objCurrDocInstance.SLIPencoding = "utf-8" #SLIP default
	objCurrDocInstance.XMLlinesep = os.linesep
	
def openFile(path):
	global objCurrDocInstance
	
	sXML = ""
	
	
	if path == "" or os.path.isfile(path) == 0:
		sys.stderr.write("File not available\n")
		return
	else:
		objCurrDocInstance = clsDocInstance()
		objCurrDocInstance.path = path
		fdXML = open(objCurrDocInstance.path, "r")

		if booVerbose:
			print "Checking for Encoding..."
		
		#get XML encoding from declaration if there
		while 1:
			sXMLStart = fdXML.readline()
			if string.strip(sXMLStart) != "":
				break
		
		reXMLEncoding = re.compile(u"encoding=(?:'|\")(\\S+)(?:'|\")", re.U | re.I)
		lsEncoding = reXMLEncoding.findall(sXMLStart)
		
		#set SLIP encoding
		if len(lsEncoding) != 0:
			objCurrDocInstance.XMLencoding = lsEncoding[0]
			if string.upper(objCurrDocInstance.XMLencoding) in ["ASCII", "US-ASCII"]:
				objCurrDocInstance.SLIPencoding = "ascii"
			else:
				objCurrDocInstance.SLIPencoding = "utf-8"
		
		
		
		#if couldn't find XML encoding, try opening with ASCII or UTF-8 or sys default, or error out 
		if objCurrDocInstance.XMLencoding == "":
			
			fdXML.seek(0)
			
			while 1:
				chunk = fdXML.read(16384)
				if not chunk:
					break
				else:
					sXML += chunk
				
				
			#sXML = fdXML.read()
			
			
			if booVerbose:
				print "Trying to open as ASCII"
			try:
				sXML = unicode(sXML, "ascii")
			except:
				if booVerbose:
					print "Not ASCII, trying utf-8..."
		
				try:
					sXML = unicode(sXML, "utf-8")
				except:
					if booVerbose:
						print "Not utf-8, trying System Default " + sys.getdefaultencoding()
					try:
						sXML = unicode(sXML, sys.getdefaultencoding())
						#opens as unicode from sys default
					except:
						raise EncodingError,  "Could not find encoding of XML document"
					else:
						if booVerbose:
							print "Opened and translated"
						objCurrDocInstance.XMLencoding = sys.getdefaultencoding()
						objCurrDocInstance.SLIPencoding = "utf-8"
				else:
					if booVerbose:
						print "Opened and translated"
					objCurrDocInstance.XMLencoding = "utf-8"
					objCurrDocInstance.SLIPencoding = "utf-8"
			else:
				if booVerbose:
					print "Opened and translated"
				objCurrDocInstance.XMLencoding = "ascii"
				objCurrDocInstance.SLIPencoding = "ascii"
		
		else:
			
			#fdXML.close()
			#fdXML = None
			fdXML.seek(0)
			fdXML = codecs.open(objCurrDocInstance.path, "r", objCurrDocInstance.XMLencoding)
			if booVerbose:
				print "Opening file..."
			
			while 1:
				chunk = fdXML.read(16384)
				if not chunk:
					break
				else:
					sXML += chunk
			
			#sXML = fdXML.read()

			if booVerbose:
				print "Reading file..."
			
			
		#find line breaker	
		fdXML.seek(0)
	 
		while 1:
			sXMLStart = fdXML.readline()
			if string.strip(sXMLStart) != "":
				break
				
		fdXML.close()
		fdXML = None
		
		if booVerbose:
			print "Initializing data..."
			print "(may take awhile if initializing a large file)"
		
		if string.find(sXMLStart, u"\r\n") != -1:
			#windows
			objCurrDocInstance.XMLlinesep = u"\r\n"
		elif string.find(sXMLStart, u"\r") != -1:
			#mac
			objCurrDocInstance.XMLlinesep = u"\r"
		elif string.find(sXMLStart, u"\n") != -1:
			#unix
			objCurrDocInstance.XMLlinesep = u"\n"


		#normalize linesep for processing
		if objCurrDocInstance.XMLlinesep != u"\n":
			sXML = string.replace(sXML, objCurrDocInstance.XMLlinesep, u"\n")

		
		#store the XML as Unicode
		objCurrDocInstance.SLIP = sliplib.xml2slip(sXML) 
		





def editFile():
	global objCurrDocInstance
	
	if booVerbose:
		print "Creating temp file for editing ..."
	
	#create temp file with current SLIP content
	pathTempSLIP = tempfile.mktemp(strSlipExtension)
	fdTempSLIP = codecs.open(pathTempSLIP, "w", objCurrDocInstance.SLIPencoding)
	
	#help content
	strHelpHeader = """\
### ----------------Start SLIDE Help (Do Not Edit or Remove)-------------------- ###
# Edit using SLiP formatting in this temporary file.  When complete, just save the file to it's original location
# and close this editor.  The changes will be updated in SLIDE, without these help lines.
### ----------------End SLIDE Help (Do Not Edit or Remove)---------------------- ###

"""
	
	
	#convert line breaks into OS friendly linebreaks for editing
	strFormattedLineBreaksSLIP = string.replace(strHelpHeader + objCurrDocInstance.SLIP, u"\n", os.linesep)
	fdTempSLIP.write(strFormattedLineBreaksSLIP)
	fdTempSLIP.close()
	
	if booVerbose:
		print "Opening temp file in editor..."
		print "\n-----------"
		print "Close editor to continue SLIDE..."
		print "-----------"
	
	#open temp file in editor   	
	os.system(strEditorCmd + " " + pathTempSLIP)
	
	if booVerbose:
		print "\n"
		print "Reading new content..."
	
	#re-open and update docInstance with new content
	fdTempSLIP = codecs.open(pathTempSLIP, "r", objCurrDocInstance.SLIPencoding) #returns Unicode
	sSLIPUnicode = fdTempSLIP.read()
	sTempSLIP = string.replace(sSLIPUnicode, os.linesep, u"\n")
	
	#remove help
	for x in range(4):
		int = string.find(sTempSLIP, "\n")
		sTempSLIP = sTempSLIP[int+1:]
	
	objCurrDocInstance.SLIP = sTempSLIP
	fdTempSLIP.close()
	
	if booVerbose:
		print "Initializing new content..."
		print "(may take awhile if initializing a large file)"
	
	objCurrDocInstance.XML = sliplib.slip2xml(objCurrDocInstance.SLIP)
	os.remove(pathTempSLIP)

def previewFile():
	global objCurrDocInstance

	pathTempXML = tempfile.mktemp(strXMLExtension)	
	fdTempXML = codecs.open(pathTempXML, "w", objCurrDocInstance.XMLencoding)
	
	#at least put in OS friendly linebreaks for formatting
	strFormattedLineBreaksXML = string.replace(objCurrDocInstance.XML, u"\n", os.linesep)
	fdTempXML.write(strFormattedLineBreaksXML)
	fdTempXML.close()

	os.system(strViewerCmd + " " + pathTempXML)
	
	
	#removes too fast for some viewers (IE), workaround with sleep for now..
	if string.upper(strViewerCmd) == "EXPLORER":
		time.sleep(5)
		while 1:
			#resolves slow loading IE
			try:
				os.remove(pathTempXML)	
			except OSError:
				time.sleep(2)
			else:
				break
	else:
		os.remove(pathTempXML)	
	

def saveFile():
	global objCurrDocInstance
	fdSaveXML = codecs.open(objCurrDocInstance.path, "w", objCurrDocInstance.XMLencoding)
	strFormattedLineBreaksXML = string.replace(objCurrDocInstance.XML, u"\n", objCurrDocInstance.XMLlinesep)
	fdSaveXML.write(strFormattedLineBreaksXML)
	fdSaveXML.close()

def saveAsFile(path):
	global objCurrDocInstance
	if path == "":
		sys.stderr.write("Non-valid filename")
		return
	else:
	
		fdSaveXML = codecs.open(path, "w", objCurrDocInstance.XMLencoding)
		strFormattedLineBreaksXML = string.replace(objCurrDocInstance.XML, u"\n", objCurrDocInstance.XMLlinesep)
		fdSaveXML.write(strFormattedLineBreaksXML)
		fdSaveXML.close()

def closeFile():
	global objCurrDocInstance
	objCurrDocInstance = None
	
def quit():
	global objCurrDocInstance
	pass



if __name__ == "__main__":
	scriptName = sys.argv[0]
	strUsage = """
Usage: 

To open editor:

	%(scriptName)s  
		- or -
	%(scriptName)s [xml file (optional)]
		
To see this help screen:

	%(scriptName)s -h
			
	""" % vars()


	if len(sys.argv) > 2:
		print strUsage
		sys.exit(1)

	if len(sys.argv) > 1:
		if sys.argv[1][:1] == "-":
			#process options
			if sys.argv[1][1:] in "hH":
				print strUsage
				sys.exit(1)
			
		else:
			fname = sys.argv[1]
			booVerbose = 1
		
	else:
		fname = ""
		booVerbose = 1
	
	main(fname)

    